<?php

/*
 * Copyright 2008 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#namespace tehframework\objects;

class PropertyEditorManager
{
	const NAME_TYPE_SEPARATOR = '.';
	
	/**
	 * @var array<string,PropertyEditor>
	 */
	protected $editors = array();
	
	/**
	 * Registers an editor for properties of the specified type and/or name.
	 * 
	 * @param  PropertyEditor $editor
	 *         An editor that will be used for editing properties
	 *         of the specified type and/or name.
	 * @param  string? $propertyType
	 *         A type of the property.
	 * @param  string? $propertyName
	 *         A name of the property.
	 * @return PropertyEditorManager
	 * @throws BadMethodCallException
	 *         If both {@link $propertyType} and {@link $propertyName}
	 *         arguments are NULL or empty.
	 * @see    editProperty()
	 */
	public function registerEditor(
		PropertyEditor $editor, $propertyType = null, $propertyName = null)
	{
		$this->editors[self::makeEditorKey($propertyType, $propertyName)] =
			$editor;
		
		return $this;
	}
	
	/**
	 * Edits the given property.
	 * 
	 * @param  mixed $value
	 *         A property to edit.
	 * @param  string? $type
	 *         The desired type of the property.
	 *         Can not be NULL if {@link $name} is NULL.
	 * @param  string? $name
	 *         An optional name of the property to edit.
	 *         If specified, it will be used along with the type of
	 *         the property to find the appropriate editor.
	 *         Can not be NULL if {@link $type} is NULL.
	 * @return mixed
	 *         An edited property.
	 * @throws BadMethodCallException
	 *         If both {@link $type} and {@link $name} arguments
	 *         are NULL or empty.
	 * @throws InvalidArgumentException
	 *         If no editor was found for the given arguments.
	 * @throws LogicException
	 *         If editor was found for given arguments, but it can not edit the
	 *         specified property (i.e. {@link PropertyEditor::canEditProperty()}
	 *         returns FALSE).
	 * @see    registerEditor()
	 */
	public function editProperty($value, $type = null, $name = null)
	{
		$editorKey = self::makeEditorKey($type, $name);
		
		if (!isset($this->editors[$editorKey]))
		{
			throw new InvalidArgumentException(sprintf(
				'No editor found for property of type [%s] and name [%s].',
				empty($type) ? 'NULL' : $type,
				empty($name) ? 'NULL' : $name
			));
		}
		
		if (!$this->editors[$editorKey]->canEditProperty($value))
		{
			throw new LogicException(sprintf(
				'Editor [%s] of type [%s] can not be used to ' .
				'edit properties of type [%s].',
				$editorKey,
				get_class($this->editors[$editorKey]),
				is_object($value) ? get_class($value) : gettype($value)
			));
		}
		
		return $this->editors[$editorKey]->editProperty($value);
	}
	
	/**
	 * @param  string? $type
	 * @param  string? $name
	 * @return string
	 * @throws BadMethodCallException
	 */
	protected static function makeEditorKey($type, $name)
	{
		$emptyType = empty($type);
		$emptyName = empty($name);
		
		if ($emptyType && $emptyName)
		{
			throw new BadMethodCallException(
				'Type and name can not be NULL or empty simultaneously.'
			);
		}
		
		if ($emptyName)
		{
			return $type;
		}
		
		if ($emptyType)
		{
			return $name;
		}
		
		return $name . self::NAME_TYPE_SEPARATOR . $type;
	}
}